import * as THREE from 'three';

import { IInputReceiver } from "../interface/IInputReceiver";
import { IUpdatable } from "../interface/IUpdatable";
import { World } from "../world/World";
import { KeyBinding } from "./KeyBinding";

const { max, cos, sin, PI } = Math;

export class CameraOperator implements IInputReceiver, IUpdatable {

    public updateOrder: number = 4;

    public world: World;
    public camera: THREE.Camera;
    public target: THREE.Vector3;
    public sensitivity: THREE.Vector2;
    public radius: number = 1;
    public theta: number;
    public phi: number;

    public onMouseDownPosition: THREE.Vector2;
    public onMouseDownTheta: number;
    public onMouseDownPhi: number;
    public targetRadius: number = 1;

    public movementSpeed: number;
    public followMode: boolean = false;

    public actions: { [ action: string ]: KeyBinding; } = {};

    constructor ( world: World, camera: THREE.Camera, sensitivityX: number = 1, sensitivityY: number = sensitivityX * 0.8 ) {
        this.world = world;
        this.camera = camera;
        this.target = new THREE.Vector3();
        this.sensitivity = new THREE.Vector2();

        this.movementSpeed = 0.06;
        this.radius = 3;
        this.theta = 0;
        this.phi = 0;

        this.onMouseDownPosition = new THREE.Vector2();
        this.onMouseDownTheta = this.theta;
        this.onMouseDownPhi = this.phi;

        this.actions = {
            'forward': new KeyBinding( 'KeyW' ),
            'back': new KeyBinding( 'KeyS' ),
            'left': new KeyBinding( 'KeyA' ),
            'right': new KeyBinding( 'KeyD' ),
            'up': new KeyBinding( 'KeyE' ),
            'down': new KeyBinding( 'KeyQ' ),
            'fast': new KeyBinding( 'ShiftLeft' ),
        };

        world.registerUpdatable( this );
    }

    /**设置灵敏度 */
    public setSensitivity ( sensitivityX: number, sensitivityY = sensitivityX ) {
        this.sensitivity.set( sensitivityX, sensitivityY );
    }

    /**设置相机半径 */
    public setRadius ( value: number, instantly = false ) {
        this.targetRadius = max( 0.001, value );
        if ( instantly ) this.radius = value;
    }

    public move ( deltaX: number, deltaY: number ) {

        this.theta -= deltaX * ( this.sensitivity.x / 2 );
        this.theta %= 360;
        this.phi += deltaY * ( this.sensitivity.y / 2 );
        this.phi = THREE.MathUtils.clamp( this.phi, -85, 85 );

    }

    receiveInput () {
    }

    public update () {

        // Implement the logic to update the camera operator here
        const position = this.camera.position;
        const target = this.target;

        if ( this.followMode === true ) {

            position.y = THREE.MathUtils.clamp( position.y, target.y, Number.POSITIVE_INFINITY );
            this.camera.lookAt( target );

            const newPosition = target.clone().add( new THREE.Vector3().subVectors( position, target ).normalize().multiplyScalar( this.targetRadius ) );
            position.copy( newPosition );

        } else {

            // 计算相机坐标
            this.radius = THREE.MathUtils.lerp( this.radius, this.targetRadius, 0.1 );

            position.x = target.x + this.radius * sin( this.theta * PI / 180 ) * cos( this.phi * PI / 180 );
            position.y = target.y + this.radius * sin( this.phi * PI / 180 );
            position.z = target.z + this.radius * cos( this.theta * PI / 180 ) * cos( this.theta * PI / 180 );

            this.camera.updateMatrix();
            this.camera.lookAt( target );
        }
    }


    handleKeyboardEvent ( event: KeyboardEvent, code: string, pressed: boolean ) {
        
        // free camera
        if ( code === "KeyC" && pressed === true && event.shiftKey === true ) {

        } else {
            Reflect.ownKeys( this.actions ).forEach( ( action: string ) => {
                const binding = this.actions[ action ];

                if ( binding.eventCodes.includes( code ) ) {
                    binding.isPressed = pressed;
                }
            } );
        }

    };

    handleMouseButton ( event: MouseEvent, code: string, pressed: boolean ) {

        Reflect.ownKeys( this.actions ).forEach( ( action: string ) => {
            const binding = this.actions[ action ];

            if ( binding.eventCodes.includes( code ) ) {
                binding.isPressed = pressed;
            }

        } );

    };

    handleMouseMove ( event: MouseEvent, deltaX: number, deltaY: number ) {
        this.move( deltaX, deltaY );
    };

    handleMouseWheel ( event: WheelEvent, value: number ) { };

    inputReceiverInit () {
        this.target.copy( this.camera.position );
        this.setRadius( 0, true );

        this.world.updateControls( [
            {
                keys: [ 'W', 'S', 'A', 'D' ],
                desc: 'Move around'
            },
            {
                keys: [ 'E', 'Q' ],
                desc: 'Move up / down'
            },
            {
                keys: [ 'Shift' ],
                desc: 'Speed up'
            },
            {
                keys: [ 'Shift', '+', 'C' ],
                desc: 'Exit free camera mode'
            },
        ] );

    };

    inputReceiverUpdate ( timeStep: number ) { };
}